// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "mlink.h"
#include "mlink_scenes.h"

#ifdef CONFIG_MLINK_SCENES_LIST_MAX_NUM
#define MLINK_SCENES_LIST_MAX_NUM CONFIG_MLINK_SCENES_LIST_MAX_NUM
#else
#define MLINK_SCENES_LIST_MAX_NUM 50
#endif
#define MLINK_SCENES_STORE_KEY    "MLINK_SCENES"

/**
 * @brief List of scenes functions
 */
typedef struct mlink_scenes {
    char name[16];
	char *alias;
    uint16_t raw_data_size;
    int delay; // 延时执行
    int content_num;
	char **execute_content;	
    struct mlink_scenes *next;
} mlink_scenes_t;

/**
 * @brief Store scenes function
 */
typedef struct {
    uint16_t size;
    char name[16];
} mlink_scenes_store_t;

static const char *TAG                 = "mlink_scenes";
static mlink_scenes_t *g_scenes_list = NULL;
extern mlink_characteristic_func_t mlink_device_get_value;

static mlink_scenes_t *mlink_scenes_parse(const char *raw_data)
{
    mdf_err_t ret                      = MDF_OK;
    mlink_scenes_t *scenes_item      = MDF_CALLOC(1, sizeof(mlink_scenes_t));
    scenes_item->raw_data_size = strlen(raw_data) + 1;
    ret = mlink_json_parse(raw_data, "name", scenes_item->name);
    MDF_ERROR_GOTO(ret < 0, EXIT, "Parse the json formatted string");
    MDF_LOGD("name: %s", scenes_item->name);
	ret = mlink_json_parse(raw_data, "alias", &scenes_item->alias);// 别名
    ret = mlink_json_parse(raw_data, "delay",	&scenes_item->delay);
	ret = mlink_json_parse(raw_data, "execute_content", &scenes_item->content_num);
    if(ret == MDF_OK && scenes_item->content_num > 0) {
        scenes_item->execute_content = MDF_MALLOC(scenes_item->content_num * sizeof(char *));
        ret = mlink_json_parse(raw_data, "execute_content", scenes_item->execute_content);
    }
	MDF_ERROR_GOTO(ret < 0, EXIT, "Parse the json formatted string");

EXIT:

    if (ret != MDF_OK) {
        if(scenes_item->content_num > 0){
            for(int i=0;i<scenes_item->content_num;i++){
                MDF_FREE(scenes_item->execute_content[i]);
            }
        }
		MDF_FREE(scenes_item->execute_content);
        MDF_FREE(scenes_item->alias);
        MDF_FREE(scenes_item);
    }

    return (ret == MDF_OK) ? scenes_item : NULL;
}

mdf_err_t mlink_scenes_store_update()
{
    mdf_err_t ret   = MDF_OK;
    int scenes_num = 0;
    mlink_scenes_store_t *scenes_store = MDF_CALLOC(MLINK_SCENES_LIST_MAX_NUM, sizeof(mlink_scenes_store_t));

    for (mlink_scenes_t *scenes_idex = g_scenes_list->next; scenes_idex; scenes_idex = scenes_idex->next, scenes_num++) {
        strncpy(scenes_store[scenes_num].name, scenes_idex->name, sizeof(scenes_store[scenes_num].name));
        scenes_store[scenes_num].size = scenes_idex->raw_data_size;
    }

    if (scenes_num > 0) {
        ret = mdf_info_save(MLINK_SCENES_STORE_KEY, scenes_store, scenes_num * sizeof(mlink_scenes_store_t));
    } else {
        ret = mdf_info_erase(MLINK_SCENES_STORE_KEY);
    }

    MDF_FREE(scenes_store);
    MDF_ERROR_CHECK(ret < 0, ESP_FAIL, "mdf_info_save, ret: %d", ret);

    return MDF_OK;
}

static mdf_err_t mlink_handle_get_scenes(mlink_handle_data_t *handle_data)
{
    mdf_err_t ret      = ESP_OK;
    char *scenes_json = NULL;
    char *raw_data     = NULL;
	int count=0;
	int index_start=0, index_end=MLINK_SCENES_LIST_MAX_NUM;
	int limit[2];
	
	ret = mlink_json_parse(handle_data->req_data, "limit", limit);
	if(ret == MDF_OK){
		MDF_ERROR_GOTO(limit[0] < 0 || limit[0] >= MLINK_SCENES_LIST_MAX_NUM, EXIT, "limit arg error");
		MDF_ERROR_GOTO(limit[1] < 0, EXIT, "limit arg error");
	
		index_start = limit[0];
		limit[1] += index_start;
		index_end = limit[1] < MLINK_SCENES_LIST_MAX_NUM ? limit[1] : MLINK_SCENES_LIST_MAX_NUM;
		MDF_LOGI("index %d %d", index_start,index_end);
	}
	
	count=0;
    for (mlink_scenes_t *scenes_idex = g_scenes_list->next; scenes_idex; scenes_idex = scenes_idex->next) {
		if( (count >= index_start) && (count < index_end)){
			raw_data = MDF_MALLOC(scenes_idex->raw_data_size);
			ret = mdf_info_load(scenes_idex->name, raw_data, scenes_idex->raw_data_size);
			MDF_ERROR_GOTO(ret != MDF_OK, EXIT, " Load the information");
			
			mlink_json_pack(&scenes_json, "[]", raw_data);
			MDF_FREE(raw_data);
		}
		count++;
    }

    if (!scenes_json) {
        return MDF_OK;
    }

    handle_data->resp_size = mlink_json_pack(&handle_data->resp_data, "scenes", scenes_json);

EXIT:
    MDF_FREE(raw_data);
    MDF_FREE(scenes_json);
    return ret;
}

mdf_err_t mlink_scenes_add(const char *scenes_raw_data)
{
    MDF_PARAM_CHECK(scenes_raw_data);
    MDF_ERROR_CHECK(!g_scenes_list, MDF_ERR_NOT_INIT, "mlink_scenes is not initialized");

    mdf_err_t ret = MDF_OK;
    mlink_scenes_t *scenes_list = g_scenes_list;
    mlink_scenes_t *scenes_item = NULL;

    scenes_item = mlink_scenes_parse(scenes_raw_data);
    MDF_ERROR_CHECK(!scenes_item, MDF_FAIL, "mlink_scenes_parse");

    for (mlink_scenes_t *scenes_idex = scenes_list->next; scenes_idex;
            scenes_list = scenes_idex, scenes_idex = scenes_idex->next) {
        if (!strcasecmp(scenes_idex->name, scenes_item->name)) {
            MDF_LOGD("remove scenes: %s", scenes_idex->name);
            ret = mdf_info_erase(scenes_idex->name);
            MDF_ERROR_CONTINUE(ret != MDF_OK, "mdf_info_erase, ret: %d", ret);

            scenes_list->next = scenes_idex->next;
            if(scenes_idex->content_num > 0){
                for(int i=0;i<scenes_idex->content_num;i++){
                    MDF_FREE(scenes_idex->execute_content[i]);
                }
            }
			MDF_FREE(scenes_idex->execute_content);
            MDF_FREE(scenes_idex->alias);
            MDF_FREE(scenes_idex);
            scenes_idex = scenes_list;
            break;
        }
    }

    scenes_item->next   = g_scenes_list->next;
    g_scenes_list->next = scenes_item;

    ret = mdf_info_save(scenes_item->name, scenes_raw_data, strlen(scenes_raw_data) + 1);
    MDF_ERROR_CHECK(ret < 0, ret, "Save the information");

    ret = mlink_scenes_store_update();
    MDF_ERROR_CHECK(ret != MDF_OK, ret, "mlink_scenes_store_update");

    return MDF_OK;
}

static mdf_err_t mlink_handle_set_scenes(mlink_handle_data_t *handle_data)
{
    mdf_err_t ret   = MDF_OK;
    int scenes_num = 0;
    char **scenes_raw_data = NULL;

    ret = mlink_json_parse(handle_data->req_data, "scenes", &scenes_num);
    MDF_ERROR_CHECK(ret != MDF_OK, ret, "Parse the json formatted string");

    scenes_raw_data = MDF_MALLOC(scenes_num * sizeof(void *));
    MDF_ERROR_CHECK(!scenes_raw_data, MDF_FAIL, "Malloc memory for scenes_raw_data");

    ret = mlink_json_parse(handle_data->req_data, "scenes", scenes_raw_data);
    MDF_ERROR_CHECK(ret != MDF_OK, ret, "Parse the json formatted string");

    for (int i = 0; i < scenes_num; ++i) {
        ret = mlink_scenes_add(scenes_raw_data[i]);
        MDF_ERROR_CHECK(ret != MDF_OK, ret, "mlink_scenes_add");

        MDF_LOGD("mlink_handle_set_scenes success");
    }
    return MDF_OK;
}

static mdf_err_t mlink_handle_remove_scenes(mlink_handle_data_t *handle_data)
{
    mdf_err_t ret         = ESP_OK;
    int scenes_num       = 0;
    char *scenes_list = NULL;

    ret = mlink_json_parse(handle_data->req_data, "name", &scenes_num);
    MDF_ERROR_CHECK(ret < 0, ret, "Parse the json formatted string");

    scenes_list = MDF_MALLOC(scenes_num);
    assert(scenes_list);

    ret = mlink_json_parse(handle_data->req_data, "name", &scenes_list);
    if(ret != MDF_OK){
        MDF_LOGW("Parse the json formatted string, ret: %d", ret);
        MDF_FREE(scenes_list);
        return ret;
    }

    for (int i = 0; i < scenes_num; ++i) {

        mlink_scenes_t *scenes_idex_prior = g_scenes_list;

        for (mlink_scenes_t *scenes_idex = scenes_idex_prior->next; scenes_idex;
                scenes_idex_prior = scenes_idex, scenes_idex = scenes_idex->next) {
            if (!strcasecmp(scenes_idex->name, scenes_list[i])) {
                MDF_LOGD("remove event: %s", scenes_idex->name);
                ret = mdf_info_erase(scenes_idex->name);
                if(ret != MDF_OK){
                    MDF_LOGW("mdf_info_erase, ret: %d", ret);
                }

                scenes_idex_prior->next = scenes_idex->next;
                if(scenes_idex->content_num > 0){
                    for(int i=0;i<scenes_idex->content_num;i++) {
                        MDF_FREE(scenes_idex->execute_content[i]);
                    }
                }
                MDF_FREE(scenes_idex->execute_content);
                MDF_FREE(scenes_idex->alias);
                MDF_FREE(scenes_idex);
                scenes_idex = scenes_idex_prior;
                break;
            }
        }
        MDF_FREE(scenes_list[i]);
    }
    MDF_FREE(scenes_list);

    ret = mlink_scenes_store_update();
    MDF_ERROR_CHECK(ret != MDF_OK, ret, "mlink_scenes_store_update");

    return ESP_OK;
}

static mdf_err_t mlink_handle_remove_all_scenes(mlink_handle_data_t *handle_data)
{
    mdf_err_t ret         = ESP_OK;

    mlink_scenes_t *scenes_idex_prior = g_scenes_list;

    for (mlink_scenes_t *scenes_idex = scenes_idex_prior->next; scenes_idex;
		scenes_idex_prior = scenes_idex, scenes_idex = scenes_idex->next) {
    
        MDF_LOGD("remove event: %s", scenes_idex->name);
        ret = mdf_info_erase(scenes_idex->name);
        MDF_ERROR_CHECK(ret < 0, ESP_FAIL, "mdf_info_erase, ret: %d", ret);

        scenes_idex_prior->next = scenes_idex->next;
        if(scenes_idex->content_num > 0){
            for(int i=0;i<scenes_idex->content_num;i++){
                MDF_FREE(scenes_idex->execute_content[i]);
            }
        }
        MDF_FREE(scenes_idex->execute_content);
        MDF_FREE(scenes_idex->alias);
        MDF_FREE(scenes_idex);
        scenes_idex = scenes_idex_prior;
    }
    ret = mlink_scenes_store_update();
    MDF_ERROR_CHECK(ret != MDF_OK, ret, "mlink_scenes_store_update");

    return ESP_OK;
}

static void delay_exec_timer_cb(TimerHandle_t timer)
{
    mlink_handle_data_t *arg = (mlink_handle_data_t *)pvTimerGetTimerID(timer);
	xQueueSend(g_mlink_handle_queue, &arg, portMAX_DELAY);
    xTimerDelete(timer, 0);
}

// 执行场景动作
static mdf_err_t execute_scenes_content(char *execute_content, int delay) {
	int ret = ESP_OK;

	mlink_handle_data_t *handle_data   = MDF_CALLOC(1, sizeof(mlink_handle_data_t));
	assert(handle_data);
	asprintf(&handle_data->req_data, "%s", execute_content);
	handle_data->req_size	  = strlen(execute_content);
	handle_data->req_fromat  = MLINK_HTTPD_FORMAT_JSON;
	handle_data->resp_fromat = MLINK_HTTPD_FORMAT_JSON;
    if(delay > 0){
        TimerHandle_t timer = xTimerCreate("scenes_delay", pdMS_TO_TICKS(delay), false, handle_data, delay_exec_timer_cb);
        xTimerStart(timer, 0);
    } else {
	    xQueueSend(g_mlink_handle_queue, &handle_data, portMAX_DELAY);
    }

    return MDF_OK;
}

mdf_err_t mlink_handle_trigger_scenes(mlink_handle_data_t *handle_data)
{
    
    mdf_err_t ret         = ESP_OK;
    int scenes_num       = 0;
    char *scenes_list[MLINK_SCENES_LIST_MAX_NUM] = {0};

	MDF_ERROR_CHECK(!g_scenes_list, MDF_ERR_NOT_INIT, "mlink_scenes is not initialized");

    ret = mlink_json_parse(handle_data->req_data, "name", &scenes_num);
    MDF_ERROR_CHECK(ret < 0, ret, "Parse the json formatted string");

    ret = mlink_json_parse(handle_data->req_data, "name", scenes_list);
    MDF_ERROR_CHECK(ret < 0, ret, "Parse the json formatted string");

    for (int i = 0; i < scenes_num; ++i) {
        mlink_scenes_t *scenes_idex_prior = g_scenes_list;
        for (mlink_scenes_t *scenes_idex = scenes_idex_prior->next; scenes_idex;
                scenes_idex_prior = scenes_idex, scenes_idex = scenes_idex->next) {
            if (!strcasecmp(scenes_idex->name, scenes_list[i])) {
                MDF_LOGD("trugger event: %s", scenes_idex->name);

                for(int i=0; i< scenes_idex->content_num; i++){
                    execute_scenes_content(scenes_idex->execute_content[i], scenes_idex->delay);
                }
				mdf_event_loop_send(MDF_EVENT_MLINK_TRIGGER_SCENES, scenes_idex);
                break;
            }
        }
        MDF_FREE(scenes_list[i]);
    }

    return MDF_OK;
}

mdf_err_t mlink_scenes_init()
{
    if (g_scenes_list) {
        return MDF_OK;
    }

    mdf_err_t ret                 = MDF_OK;
    g_scenes_list                = MDF_CALLOC(1, sizeof(mlink_scenes_t));
    mlink_scenes_t *scenes_item = NULL;
    mlink_scenes_store_t *scenes_store = MDF_CALLOC(MLINK_SCENES_LIST_MAX_NUM, sizeof(mlink_scenes_store_t));

    if (mdf_info_load(MLINK_SCENES_STORE_KEY, scenes_store,
                      MLINK_SCENES_LIST_MAX_NUM * sizeof(mlink_scenes_store_t)) == MDF_OK) {
        for (int i = 0; i < MLINK_SCENES_LIST_MAX_NUM && scenes_store[i].size; ++i) {
            char *scenes_raw_data = MDF_CALLOC(1, scenes_store[i].size);
            ret = mdf_info_load(scenes_store[i].name, scenes_raw_data, scenes_store[i].size);
            if(ret != MDF_OK){
                MDF_LOGW("Load the information %s, ret: %d", scenes_store[i].name, ret);
                MDF_FREE(scenes_raw_data);
                continue;
            }

            scenes_item = mlink_scenes_parse(scenes_raw_data);
            if(ret != MDF_OK){
                MDF_LOGW("mlink_scenes_parse, ret: %d", ret);
                MDF_FREE(scenes_raw_data);
                continue;
            }
            MDF_FREE(scenes_raw_data);

            scenes_item->next   = g_scenes_list->next;
            g_scenes_list->next = scenes_item;
        }
    }

    MDF_FREE(scenes_store);

    mlink_set_handle("set_scenes", mlink_handle_set_scenes);
    mlink_set_handle("get_scenes", mlink_handle_get_scenes);
    mlink_set_handle("remove_scenes", mlink_handle_remove_scenes);
	mlink_set_handle("remove_all_scenes", mlink_handle_remove_all_scenes);
	mlink_set_handle("trigger_scenes", mlink_handle_trigger_scenes);

    return ESP_OK;
}

bool mlink_scenes_is_exist()
{
    return (!g_scenes_list || g_scenes_list->next == NULL) ? false : true;
}

int mlink_scenes_get_num()
{
	int count=0;
	if(!mlink_scenes_is_exist())
		return 0;
	
    for (mlink_scenes_t *scenes_idex = g_scenes_list->next; scenes_idex; scenes_idex = scenes_idex->next) {
		count++;
    }
	return count;
}

char *mlink_scenes_get_name(void *scenes)
{
	if(scenes)
		return ((mlink_scenes_t *)scenes)->name;
	return NULL;
}

char *mlink_scenes_get_alias(void *scenes)
{
	if(scenes)
		return ((mlink_scenes_t *)scenes)->alias;
	return NULL;
}

